/*
 * MemoryMonitor.java 1.0.0Build00003 2010/10/31
 *
 * Copyright 2011 Ice Spring All rights reserved.
 * mrogrape.com PROPRIETARY.
 */
package com.mrogrape.notepad.ui;

import java.awt.EventQueue;
import java.text.NumberFormat;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JProgressBar;


/**
 * MemoryMonitor是一个用于查看JVM内存使用状况的GUI组件。
 * 
 * <p>
 * MemoryMonitor以进度条的方式来显示内存的使用情况。其继承自javax.Swing.JProgressBar类。
 * 所以你可以像添加一般的进度条那样方便的把它添加在你的GUI程序上。
 * 
 * <p>
 * 默认情况下，在MemoryMonitor上会以“已经使用/总内存”的格式绘制表示内存使用状况的字符串。
 * 你可以更改这个设置或者禁用绘制。禁用绘制的方法和禁用JProgressBar绘制的方式相同。
 * 如果需要更改，请使用{@link #setMessageFormat(String)}来设置，
 * 如果想在自己的消息格式中添加动态内容，MemoryMonitor类提供了以下消息占位符供使用，
 * MemoryMonitor会在绘制字符串时自动将其替换为对应的内容：
 * <ul>
 * <li>MemoryMonitor.MAX_MEMORY : 表示当前JVM试图使用的最大内存量。 
 * <li>MemoryMonitor.TOTAL_MEMORY : 表示当前JVM中的内存总量。 
 * <li>MemoryMonitor.FREE_MEMORY : 表示当前JVM中的空闲内存。 
 * <li>MemoryMonitor.USAGE_MEMORY : 表示当前已使用的内存。 
 * <li>MemoryMonitor.USAGE_PERCENT_MEMORY : 表示当前JVM已经使用的内存占内存总量的百分比。
 * </ul>
 * 
 * @author Ice Spring
 * @version 1.0
 *
 */
public class MemoryMonitor extends JProgressBar{

	/** SerialVersionUID */
	private static final long serialVersionUID = 1L;

	// ************************************************************************
	// 信息占位符
	// ************************************************************************

	/**
	 * 表示当前JVM试图使用的最大内存量。
	 * 
	 * @see #setMessageFormat(String)
	 */
	public static final String MAX_MEMORY = "{MAX}";

	/**
	 * 表示当前JVM中的内存总量。
	 * 
	 * @see #setMessageFormat(String)
	 */
	public static final String TOTAL_MEMORY = "{TOTAL}";

	/**
	 * 表示当前JVM中的空闲内存。
	 * 
	 * @see #setMessageFormat(String)
	 */
	public static final String FREE_MEMORY = "{FREE}";

	/**
	 * 表示当前已使用的内存。
	 * 
	 * @see #setMessageFormat(String)
	 */
	public static final String USAGE_MEMORY = "{USAGE}";

	/**
	 * 表示当前JVM已经使用的内存占内存总量的百分比。
	 * 
	 * @see #setMessageFormat(String)
	 */
	public static final String USAGE_PRECENT_MEMORY = "{USAGE_PERCENT}";

	// ************************************************************************
	// 属性键
	// ************************************************************************

	/**
	 * 属性：数据更新周期。
	 */
	public static final String PROPERTY_PEROID = "peroid";

	/**
	 * 属性：信息显示格式。
	 */
	public static final String PROPERTY_MESSAGE_FORMAT = "messageFormat";

	// ************************************************************************
	// 默认值
	// ************************************************************************

	/** 默认数据更新周期 */
	private static final int DEFAULT_PEROID = 1000;

	/** 默认信息显示格式 */
	private static final String DEFAULT_MESSAGE_FORMAT = MemoryMonitor.USAGE_MEMORY
	+ "/" + MemoryMonitor.TOTAL_MEMORY;

	/** 没有启动时显示的消息 */
	private static final String DEFAULT_TEXT = "NOT READY";

	// ************************************************************************
	// 消息
	// ************************************************************************

	/** 消息：周期值不能小于或等于0. */
	private static final String MSG0001SE = "Period value cannot less or equals 0.";

	/** 消息：消息显示格式不能为空。 */
	private static final String MSG0002SE = "Message format cannot be empty.";

	// ************************************************************************
	// 构造方法
	// ************************************************************************

	/**
	 * 构造一个默认的内存监视器。
	 */
	public MemoryMonitor() {

		this(MemoryMonitor.DEFAULT_PEROID);
	}

	/**
	 * 构造一个内存监视器并指定数据的更新周期。
	 * 
	 * @param peroid 数据更新周期，以毫秒为单位，不可小于或等于0。
	 */
	public MemoryMonitor(int peroid) {

		this(peroid, MemoryMonitor.DEFAULT_MESSAGE_FORMAT);
	}

	/**
	 * 构造一个内存监视器并指定信息显示格式。
	 * 
	 * @param messageFormat 信息的显示格式，不可为空。
	 */
	public MemoryMonitor(String messageFormat) {

		this(MemoryMonitor.DEFAULT_PEROID, messageFormat);
	}

	/**
	 * 构造一个内存监视器并指定数据更新周期和信息显示格式。
	 * 
	 * @param peroid 数据更新周期，以毫秒为单位，不可小于或等于0。
	 * @param messageFormat 信息的显示格式，不可为空。
	 */
	public MemoryMonitor(int peroid, String messageFormat) {

		this.setPeroid(peroid);
		this.setMessageFormat(messageFormat);

		this.init();
	}

	// ************************************************************************
	// 公有方法
	// ************************************************************************

	/**
	 * 重写此方法以阻止手动设置。
	 * 
	 * <p>这个值由此类自动设置。
	 */
	@Override
	public void setMaximum(int n) {

		// Do Nothing
		// 
	}

	/**
	 * 重写此方法以阻止手动设置。
	 * 
	 * <p>这个值由此类自动设置。
	 */
	@Override
	public void setMinimum(int n) {

		// Do Nothing
	}

	/**
	 * 重写此方法以阻止手动设置。
	 * 
	 * <p>这个值由此类自动设置。
	 */
	@Override
	public void setValue(int n) {

		// Do Nothing
	}

	/**
	 * 启动监视器以开始监视。
	 */
	public void startMonitoring() {

		if(this.isRunning){
			return;
		}

		this.sensor = new Sensor();
		this.timer.schedule(this.sensor, 0, this.peroid);
		this.isRunning = true;
	}

	/**
	 * 停止监视器。
	 */
	public void stopMonitoring() {

		if(!this.isRunning){
			return;
		}

		this.sensor.cancel();
		this.isRunning = false;
	}

	/**
	 * 获取数据更新周期。
	 * 
	 * @return 数据更新周期，以毫秒为单位，不会小于或等于0。
	 */
	public int getPeroid() {

		return this.peroid;
	}

	/**
	 * 设置数据更新周期。
	 * 
	 * <p>
	 * 注意，更新周期更新后，如果当前监视器已经启动，则更该不会立即生效。
	 * 
	 * @param 数据更新周期，以毫秒为单位，不可小于或等于0。
	 */
	public void setPeroid(int peroid) {

		if(peroid <= 0){
			throw new IllegalArgumentException(MemoryMonitor.MSG0001SE);
		}

		this.peroid = peroid;
	}

	/**
	 * 获取信息显示格式。
	 * 
	 * @return 信息显示格式，不会为空。
	 */
	public String getMessageFormat() {

		return this.messageFormat;
	}

	/**
	 * 设置信息显示格式。
	 * 
	 * <p>
	 * 这个信息会在进度条上面显示。除了显示静态的文本，还可通过在文本中包含预定义的
	 * 占位符的方式来显示动态内容。本类中提供以下五个预定义的项目：
	 * <ul>
	 * <li> MemoryMonitor.MAX_MEMORY : 表示当前JVM试图使用的最大内存量。
	 * <li> MemoryMonitor.TOTAL_MEMORY : 表示当前JVM中的内存总量。
	 * <li> MemoryMonitor.FREE_MEMORY : 表示当前JVM中的空闲内存。
	 * <li> MemoryMonitor.USAGE_MEMORY : 表示当前已使用的内存。
	 * <li> MemoryMonitor.USAGE_PERCENT_MEMORY : 表示当前JVM已经使用的内存占内存总量的百分比。
	 * </ul>
	 * 
	 * <p>
	 * 要使用它们，在设置的信息字符串中把需要显示动态内容的地方设置为以上的占位符即可。
	 * 
	 * @return 信息显示格式，不可为空。
	 */
	public void setMessageFormat(String messageFormat) {

		if(messageFormat == null || messageFormat.isEmpty()){
			throw new IllegalArgumentException(MemoryMonitor.MSG0002SE);
		}

		this.messageFormat = messageFormat;
	}

	/**
	 * 查看此监视器是否处于监控状态。
	 * 
	 * @return 用以表示此监视器是否处于监控状态的boolean值：
	 * 			<ul>
	 * 			<li> true  : 正在运行。
	 * 			<li> false : 没有运行。
	 * 			</ul>
	 */
	public boolean isMonitoring() {

		return this.isRunning;
	}





	private void init(){

		// instant local variables
		this.runtime = Runtime.getRuntime();
		this.timer = new Timer();
		this.sensor = new Sensor();

		// init progress bar
		super.setStringPainted(true);
		super.setString(MemoryMonitor.DEFAULT_TEXT);
		super.setMinimum(0);

	}

	private String getViewText(){

		String view = this.messageFormat.replace(MemoryMonitor.FREE_MEMORY, this.getFreeMemory())
		.replace(MemoryMonitor.TOTAL_MEMORY,this.getTotalMemory())
		.replace(MemoryMonitor.MAX_MEMORY, this.getMaxMemory())
		.replace(MemoryMonitor.USAGE_MEMORY, this.getUsageMemory())
		.replace(MemoryMonitor.USAGE_PRECENT_MEMORY, this.getUsagePercent());


		return view;
	}

	private String getFreeMemory(){

		return this.formatContent(this.runtime.freeMemory());
	}

	private String getTotalMemory(){

		return this.formatContent(this.runtime.totalMemory());
	}

	private String getMaxMemory(){

		return this.formatContent(this.runtime.maxMemory());
	}

	private String getUsageMemory(){

		// get the total and free memory
		long total = this.runtime.totalMemory();
		long free = this.runtime.freeMemory();

		// calect the memory in using.
		long usage = total - free;

		return this.formatContent(usage);
	}

	private String getUsagePercent(){

		// get the total and free memory
		long total = this.runtime.totalMemory();
		long free = this.runtime.freeMemory();

		long usage = total - free;

		NumberFormat nf = NumberFormat.getInstance();
		nf.setMaximumFractionDigits(1);
		String percent = nf.format(((double) usage / total)*100)+"%";


		return percent;


	}

	private String formatContent(long bytes){

		String re = null;

		NumberFormat nf = NumberFormat.getInstance();
		nf.setMaximumFractionDigits(2);
		nf.setMinimumFractionDigits(0);

		if (bytes < 1024) {
			re = bytes + "B";
		} else if (bytes < 1048576) {
			double d = (double) bytes / 1024;
			re = nf.format(d) + "KB";
		} else if (bytes < 1073741824) {
			double d = (double) bytes / 1048576;
			re = nf.format(d) + "MB";
		} else {
			double d = (double) bytes / 1073741824;
			re = nf.format(d) + "GB";
		}
		return re;
	}

	private void refreshMemoryInfo(){

		final int total = (int) this.runtime.totalMemory();
		final int usage = (int) (total - this.runtime.freeMemory());
		final String text = this.getViewText();

		// Refresh Progress Bar
		EventQueue.invokeLater(new Runnable(){

			@Override
			public void run() {

				MemoryMonitor.super.setMaximum(total);
				MemoryMonitor.super.setValue(usage);
				MemoryMonitor.super.setString(text);

			}

		});
	}

	/**
	 * 用于触发更新事件来更新数据。
	 * 
	 * @author Ice Spring
	 *
	 */
	private class Sensor extends TimerTask{

		@Override
		public void run() {

			MemoryMonitor.this.refreshMemoryInfo();
		}

	}


	private Runtime runtime = null;
	private Timer timer = null;
	private TimerTask sensor = null;

	private int peroid = MemoryMonitor.DEFAULT_PEROID;
	private boolean isRunning = false;
	private String messageFormat = MemoryMonitor.DEFAULT_MESSAGE_FORMAT;


}
